﻿using System;
using System.Diagnostics;
using System.Net.Sockets;
using System.Threading;
using Netbits.Common.Collections;
using Netbits.Common.Conditions;
using Netbits.Common.Threading;

namespace Netbits.Common.Network
{
	/// <summary>
	/// Represents a TCP Server Client Session
	/// </summary>
	public abstract class TcpServerSession : IDisposable
	{
		#region Private Classes

		internal sealed class ReceiveData
		{
			#region Private/Public Variables

			public Socket WorkSocket = null;
			public readonly byte[] Buffer;
			public int Size;

			#endregion

			#region Constructor

			public ReceiveData( int Size_ )
			{
				Buffer = new byte[Size_];
			}

			#endregion
		}

		internal sealed class SendData : IDisposable
		{
			#region Private/Public Variables

			bool _Disposed;
			public byte[] Data;
			public int IndexOf;
			public int Bytes;

			public SocketError Error;
			public ManualResetEventSlim IsSent;

			#endregion

			#region Constructor

			public SendData()
			{
				IsSent = new ManualResetEventSlim( false );
			}

			#endregion

			#region IDisposable

			public void Dispose()
			{
				if( !_Disposed )
				{
					Data = null;
					if( IsSent != null )
					{
						IsSent.Dispose();
						IsSent = null;
					}

					_Disposed = true;
				}

				GC.SuppressFinalize( this );
			}

			#endregion
		}

		#endregion

		#region Private Variables

		protected LockedQueueExtended<ReceiveData> _ReceivedQueue;
		protected LockedQueueExtended<SendData> _SendQueue;
		protected System.Net.Sockets.TcpClient _Client;
		readonly Guid _Id;
		SaveEventHandler<TcpServerSession> _EventDisconnected;

		protected int _ReceivedPackageCount;
		protected int _ReceiveLock;
		protected int _SendPackageCount;
		protected int _SendLock;
		bool _Disposed;

		#endregion

		#region Constructors

		/// <summary>
		/// Create new instance for this session object.
		/// </summary>
		/// <param name="Client_">TcpClient Socket instance</param>
		public TcpServerSession( System.Net.Sockets.TcpClient Client_ )
		{
			_ReceivedQueue = new LockedQueueExtended<ReceiveData>();
			_SendQueue = new LockedQueueExtended<SendData>();
			_EventDisconnected = new SaveEventHandler<TcpServerSession>();
			_Id = Guid.NewGuid();
			_Client = Client_;
		}

		#endregion

		#region Public Properties

		/// <summary>
		/// Notify if the client is disconnected
		/// </summary>
		public SaveEventHandler<TcpServerSession> Disconnected
		{
			get
			{
				return _EventDisconnected;
			}
		}

		/// <summary>
		/// Get the current session-id
		/// </summary>
		public Guid Id
		{
			get
			{
				return _Id;
			}
		}

		#endregion

		#region Public Functions

		/// <summary>
		/// Send the specified buffer to the client
		/// </summary>
		/// <param name="Buffer_">Buffer to send</param>
		public virtual void Send( byte[] Buffer_ )
		{
			Condition.Requires( Buffer_, "Buffer_" ).IsNotNull();

			Send( Buffer_, 0, Buffer_.Length );
		}

		/// <summary>
		/// Send the specified buffer to the client with specified index and size.
		/// </summary>
		/// <param name="Buffer_">The buffer to send</param>
		/// <param name="IndexOf_">Start sending at index-of position</param>
		/// <param name="Size_">Size amount of bytes</param>
		public virtual void Send( byte[] Buffer_, int IndexOf_, int Size_ )
		{
			Send( Buffer_, IndexOf_, Size_, Timeout.Infinite );
		}

		/// <summary>
		/// Send the specified buffer to the client with specified index and size with maximum timeout-milliseconds
		/// </summary>
		/// <param name="Buffer_">The buffer to send</param>
		/// <param name="IndexOf_">Start sending at index-of position</param>
		/// <param name="Size_">Size amount of bytes</param>
		/// <param name="Timeout_">Timeout in milliseconds or Timeout.Infinite</param>
		public virtual void Send( byte[] Buffer_, int IndexOf_, int Size_, int Timeout_ )
		{
			Condition.Requires( Buffer_, "Buffer_" ).IsNotNull();
			Condition.Requires( IndexOf_, "IndexOf_" ).IsInRange( 0, Buffer_.Length );
			Condition.Requires( Size_, "Size_" ).IsLessThan( (Buffer_.Length - IndexOf_) + 1 );

			_IsConnected();

			SendData sd = new SendData()
			{
				Bytes = Size_,
				IndexOf = IndexOf_,
				Data = Buffer_
			};

			_SendQueue.Enqueue( sd );
			Interlocked.Increment( ref _SendPackageCount );
			
			if( !sd.IsSent.Wait( Timeout_ ) )
				throw new SocketException( (int)SocketError.TimedOut );

			if( sd.Error != SocketError.Success )
				throw new SocketException( (int)sd.Error );
		}

		/// <summary>
		/// Send the specified buffer to the client asynchronous
		/// </summary>
		/// <param name="Buffer_">Buffer to send</param>
		public virtual void SendAsync( byte[] Buffer_ )
		{
			Condition.Requires( Buffer_, "Buffer_" ).IsNotNull();

			SendAsync( Buffer_, 0, Buffer_.Length );
		}

		/// <summary>
		/// Send the specified buffer to the client with specified index and size asynchronous
		/// </summary>
		/// <param name="Buffer_">The buffer to send</param>
		/// <param name="IndexOf_">Start sending at index-if position</param>
		/// <param name="Size_">Size amount of bytes</param>
		public virtual void SendAsync( byte[] Buffer_, int IndexOf_, int Size_ )
		{
			Condition.Requires( Buffer_, "Buffer_" ).IsNotNull();
			Condition.Requires( IndexOf_, "IndexOf_" ).IsInRange( 0, Buffer_.Length );
			Condition.Requires( Size_, "Size_" ).IsLessThan( (Buffer_.Length - IndexOf_) + 1 );

			_IsConnected();

			SendData sd = new SendData()
			{
				Bytes = Size_,
				IndexOf = IndexOf_,
				Data = Buffer_
			};
			_SendQueue.Enqueue( sd );
			Interlocked.Increment( ref _SendPackageCount );
		}

		/// <summary>
		/// Disconnect the client.
		/// </summary>
		public virtual void Disconnect()
		{
			try
			{
				if( _Client == null || _Client.Client == null || !_Client.Connected )
					throw new Exception( "disconnected" );

				_Client.Close();
			}
			finally
			{
				_EventDisconnected.Notify( this );
				_SendQueue.Clear();
				_ReceivedQueue.Clear();
			}
		}

		#endregion

		#region Protected Functions

		/// <summary>
		/// Must be override to handle incoming data.
		/// </summary>
		/// <param name="Data_">Received data buffer</param>
		/// <param name="Size_">The size the was received</param>
		protected virtual void __Receive( byte[] Data_, int Size_ )
		{
		}

		/// <summary>
		/// If client receive is started (init from the client is done) this function is called.
		/// </summary>
		protected virtual void __ClientInitDone()
		{
		}

		/// <summary>
		/// Dispose all client resources
		/// Must call from inhered class
		/// </summary>
		/// <param name="Disposing_">Dispose managed resources</param>
		protected virtual void Dispose( bool Disposing_ )
		{
			if( !_Disposed )
			{
				if( Disposing_ )
				{
					if( _ReceivedQueue != null )
					{
						_ReceivedQueue.Dispose();
						_ReceivedQueue = null;
					}

					if( _SendQueue != null )
					{
						SendData sd = _SendQueue.Dequeue();
						while( sd != null )
						{
							sd.Dispose();
							sd = _SendQueue.Dequeue();
						}

						_SendQueue.Dispose();
						_SendQueue = null;
					}

					if( _Client != null )
					{
						_Client.Close();
						_Client = null;
					}

					if( _EventDisconnected != null )
					{
						_EventDisconnected.Dispose();
						_EventDisconnected = null;
					}
				}

				_Disposed = true;
			}
		}

		#endregion

		#region Internal Function

		/// <summary>
		/// Returns the Send Queue
		/// </summary>
		internal LockedQueueExtended<SendData> SendQueue
		{
			get
			{
				return _SendQueue;
			}
		}

		/// <summary>
		/// Returns the Receive Queue
		/// </summary>
		internal LockedQueueExtended<ReceiveData> ReceiveQueue
		{
			get
			{
				return _ReceivedQueue;
			}
		}

		/// <summary>
		/// This function was called from the TCP-server to handle all send data.
		/// </summary>
		internal virtual void HandleSend()
		{
			if( _SendPackageCount == 0 || _Disposed )
				return;

			if( Interlocked.CompareExchange( ref _SendLock, 1, 0 ) != 0 )
				return;

			try
			{
				int maxHandleCount = 10;
				if( maxHandleCount > _SendPackageCount )
					maxHandleCount = _SendPackageCount;

				SendData sd = _SendQueue.Dequeue();
				while( sd != null )
				{
					int sent = 0;
					int index = sd.IndexOf;
					SocketError error;
					while( sent != sd.Bytes )
					{
						sent += _Client.Client.Send( sd.Data, index + sent, sd.Bytes - sent, SocketFlags.None, out error );

						if( error == SocketError.WouldBlock )
						{
							Thread.Sleep( 1 );
							continue;
						}

						if( error != SocketError.Success )
						{
							sd.Error = error;
							sd.IsSent.Set();
							break;
						}

						sd.Error = SocketError.Success;
						sd.IsSent.Set();
					}

					--maxHandleCount;
					if( Interlocked.Decrement( ref _SendPackageCount) == 0 || maxHandleCount == 0 )
						return;

					sd = _SendQueue.Dequeue();
				}
			}
			catch( Exception Ex_ )
			{
				Debug.WriteLine( Ex_ );
			}
			finally
			{
				Interlocked.Exchange( ref _SendLock, 0 );
			}
		}

		/// <summary>
		/// This function was called from the TCP-Server to handle all incoming data.
		/// </summary>
		internal void HandleReceive()
		{
			if( Interlocked.CompareExchange( ref _ReceiveLock, 1, 0 ) != 0 || _Disposed )
				return;

			try
			{
				int maxHandleCount = 10;
				ReceiveData so = _ReceivedQueue.Dequeue();
				while( so != null )
				{
					__Receive( so.Buffer, so.Size );

					--maxHandleCount;
					if( _ReceivedQueue.Count == 0 || maxHandleCount == 0 )
						return;

					so = _ReceivedQueue.Dequeue();
					if( so == null )
						break;
				}
			}
			catch( Exception Ex_ )
			{
				Debug.WriteLine( Ex_ );
			}
			finally
			{
				Interlocked.Exchange( ref _ReceiveLock, 0 );
			}
		}

		/// <summary>
		/// Activate data-receiving from the socket.
		/// </summary>
		internal void StartReceive()
		{
			_StartReceive();
			__ClientInitDone();
		}

		#endregion

		#region Private Functions

		void _IsConnected()
		{
			if( _Client.Client == null || _Client.Client.Connected == false )
				throw new SocketException( (int)SocketError.NotConnected );
		}

		void _StartReceive()
		{
			try
			{
				if( _Client == null || _Client.Client == null || !_Client.Connected )
					throw new Exception( "disconnected" );

				ReceiveData so = new ReceiveData( _Client.ReceiveBufferSize );
				so.WorkSocket = _Client.Client;
				so.WorkSocket.BeginReceive( so.Buffer, 0, so.Buffer.Length, SocketFlags.None, _EndReceive, so );
			}
			catch
			{
				Disconnect();
			}
		}

		void _EndReceive( IAsyncResult Result_ )
		{
			try
			{
				ReceiveData so = (ReceiveData)Result_.AsyncState;

				int receivedBytes = so.WorkSocket.EndReceive( Result_ );
				if( receivedBytes == 0 )
				{
					Disconnect();
					return;
				}

				so.Size = receivedBytes;
				_ReceivedQueue.Enqueue( so );
				Interlocked.Increment( ref _ReceivedPackageCount );

				_StartReceive();
			}
			catch( ObjectDisposedException )
			{
			}
			catch
			{
				Disconnect();
			}
		}

		#endregion

		#region IDisposable

		/// <summary>
		/// Dispose client resources
		/// </summary>
		public void Dispose()
		{
			Dispose( true );
			GC.SuppressFinalize( this );
		}

		#endregion
	}
}
